home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1998 November: Tool Chest / Dev.CD Nov 98 TC.toast / Sample Code / QuickTime / Show Movie / ReadMe- Show Movie next >
Encoding:
Text File  |  1995-11-20  |  19.9 KB  |  396 lines  |  [TEXT/ttxt]

  1. Show Movie
  2.  
  3. By : Don Swatman & Jason Hodges-Harris, Developer Technical Support
  4.     
  5. Copyright:    © 1995 by Apple Computer, Inc. All rights reserved.
  6.  
  7.  
  8. What it does
  9. "Show Movie" is a small application designed to load and play movies. It demonstrates several useful features in QuickTime and ways to use them:
  10.     -Creating a Time Base call back
  11.     -Setting call backs based on the length of a movie.
  12.     -Automatically closing a movie once it has finished playing
  13.     -Making a loop within a movie.
  14.     -Changing the rate of a movie while it is playing.
  15.     -Slaving one movie to another.
  16.     -Offsetting the start frame of a slave movie.
  17.     -Delaying the start of a slave movie playing
  18.  
  19. It achieves the above by using Time Base call backs documented in Inside Macintosh QuickTime pages 2-335 to 2-341, 2-364
  20.  
  21. It also demonstrates
  22.   Ways to pass data to the call backs and dispose of it once the movie has finished.
  23.   How to create a default button on a dialog without using user items.
  24.  
  25. It's not intended to be a definitive 'document' on how to implement these features, but illustrates one way of doing it.  
  26.  
  27. How to use the Application
  28.  
  29. There are two ways to open a window. "Open Movie…" opens an options dialog box, uses standard file to select a movie, then opens a movie in a window. "Open Master & Slave…" opens the options dialog (which is the same as above with a couple of extra items enabled), then asks for two movies and opens them in two windows titled "Master" and "Slave". They can be the same movie; they don't have to be, though it helps if they are roughly the same length. You can close the movie by selecting the go away box or "close" menu item. If you close a "Master" window, it will also close it's "slave".  New movies can be opened while others are still playing.
  30.  
  31. Options Dialog
  32.  
  33. "Close at end"
  34. When set, the window will close when it's movie has finished playing. If it is a "Master" window then it's slave will close as well.
  35.  
  36. "Has movie controller"
  37. Selecting it will display the standard movie controller underneath the movie. If you're opening a master and slave, only the master will receive a controller. If the movie doesn't have a controller, then it will autostart when the movie is opened.
  38.  
  39. "20 Seconds in, Loop back to 10"
  40. After the movie has played for 20 seconds, it jumps back to 10 seconds from the start.
  41.  
  42. "Change Movie Rate"
  43. This is a pop up menu which has three items. "Don't Change" means nothing will happen. "on Thirds" plays the movie at normal speed for the first 1/3 of the movie, double speed for the middle third and half speed for the final third. Note that this 1/3 refer to the original duration. For example if the movie is 60 seconds long, the first third will play for 20 seconds, the second third (at double speed) will take 10 seconds and the final third (at half speed) will take 40 seconds giving a total play time of 70 seconds! The final item "on 10 Seconds" plays the first 10 seconds at normal speed, the next 10 seconds at double speed and the remainder of the movie at half speed. Not that the second 10 second block will take only 5 seconds to play as the timings refer to the original durations of the movie.
  44.  
  45. The next two items are only available for master & slave movies
  46. "Slave Ahead by"
  47. This is a pop up that decides what frame the slave will start on. It has three options "in Sync","One Third" and "10 seconds". "in Sync" will make the slave start at the beginning. If set to "One Third", the slave will start at 1/3 of it's duration. "10 seconds" and the slave start frame will 10 seconds from it's start.
  48.  
  49. "Delay Slave Start"
  50. This pop up delays when the slave actually starts playing. Again it has three options. "No Delay" will make the slave start playing at the same time as the master. "One Third" will make the slave start one third of the way through the master movie. "10 Seconds" will delay the start of the movie for 10 seconds.
  51.  
  52. Using combinations of the above two can generate interesting effects. Using "Delay Slave Start" on it's own will run the slave movie behind the Master which is the opposite of "Slave ahead by". If both are set the same, then the slave will delay it's start, then run in sync with the master.
  53.  
  54.  
  55. "Help"
  56. This changes when you release the mouse and displays a (very) brief explanation about what  setting you have just changed and what it's new state does.
  57.  
  58.  
  59. Building ShowMovie
  60.  
  61. Show Movie compiles under :
  62.  
  63.     Metrowerks CodeWarrior 7
  64.     Symantec C++ 8.0.1
  65.     Symantec 7.0.4
  66.   MPW E.T.O. #18- 'Latest MPW': MPW C, PPCC, Symantec C++ for MPW and MrC.
  67.  
  68. The Symantec environments are using a slightly older version of the Universal Interfaces than MPW and CodeWarrior. You will need to use a later version of the Universal Interfaces than is provided with the Symantec products, or make some changes to the source code. To change the Universal Interfaces, simply place brackets around the existing folder and place the folder containing the later version into the same folder as the existing ones. The brackets will prevent the development environment from using the files contained within the old folder.
  69.  
  70. An MPW make file to build a 'fat' binary of ShowMovie using 'Latest MPW' is included. This defaults to using MPW C and PPCC. You can assign the {C} and {PPCC} variables to SC and MrC at the top of the make file if you have these compilers installed. You will need to alter the compiler options as appropriate and use the 'Latest MPW' interfaces and libraries rather than SCLibraries and SCIncludes. To build with 'PreRelease MPW' create a make file using 'Create Build Commands' and ensure that 'qd' is defined in all cases, since the PreRelease MPW Libraries do not define 'qd'. 
  71.  
  72. Files
  73.  
  74. Show Movie.c
  75. - contains "main" function
  76. - handles the event loop
  77.  
  78. WindStuff.c
  79. - creates and disposes of windows
  80. - general window handling
  81. - creates and handles the About Box
  82.  
  83. MenuStuff.c
  84. - creates the menu bar
  85. - handles clicks within the menu bar
  86. - calls code to create windows and setup movies depending on the options
  87.  
  88. MovieStuff.c
  89. - creates and disposes of information relating to movies
  90. - handles events relating to movies
  91. - contains code to set up any options that have been requested
  92.  
  93. MoviePrefs.c
  94. - handles the movie's options dialog
  95.  
  96. WindStuff.h, MenuStuff.h, MoviePrefs.h & MovieStuff.h
  97. - header files for above
  98.  
  99. Show Movie.rsrc
  100. - resources used  by show movie
  101.  
  102.  
  103.  
  104. Data Storage
  105.  
  106. DocMovieInfoType, *DocMovieInfoPtr, **DocMovieInfoHndl
  107.  
  108. This contains all the information about a movie within it's window and hangs of the window's refCon. To retrieve it from the window record:
  109.         hDocMovieInfo = (DocMovieInfoHndl)GetWRefCon ( pWindow );
  110. DocMovieInfo also contains a handle to the first item in a chain of CallBackInfoHdl.
  111.  
  112. CallBackInfoType, *CallBackInfoPtr, **CallBackInfoHdl
  113.  
  114. This is a linked list of all the data used by a callback. At the end of the record it includes a variable sized block of data used by the call back itself. The CallBackInfoHdl can be passed in the "refCon" parameter of "CallMeWhen" and it is passed into the call back in the "refCon" parameter.
  115.  CallBackInfoType also has to contain all the information used by "CallMeWhen" as once a call back has been invoked, "CancelCallBack" is called by the tool box. This removes the it from the time base. Therefore, if we want this call back to happen again (e.g. we're creating a loop), then we need to reschedule it, so the call back can be invoked again. This is done by calling "CallMeWhen" with the stored parameters.
  116.  
  117. How the interesting stuff works
  118.  
  119. Creating a Time Base call back
  120.  
  121. Most of the interesting stuff in Show Movie is handled by time based call backs. These call backs can be invoked at predetermined times, when the movie's rate of playback reaches a specified value, when the movie jumps, or at the start or end of the movie. In general the call backs used in show movie, are triggered at a specified time.
  122. First you need to get the movies time base. Use "GetMovieTimeBase" to extract this from the movie. You will also probably need the movies time scale, so also call "GetMovieTimeScale". In Show Movie this is done when the movie is created.
  123. Now you can create your own call back. Use:
  124.  
  125. QTCallBack myCallBack;
  126.  
  127. myCallBack = NewCallBack ( moviesTimeBase, callBackAtTime);
  128.  
  129. "callBackAtTime" is a constant which causes it to be invoked at a specified time. If it fails, it will return nil, so check for that. Next I need to calculate when I want to invoke my call back. By multiplying the time (in seconds) by the movies time scale we'll get a value we can use.
  130. Finally to create the call back, use "CallMeWhen"
  131.  
  132.     theErr = CallMeWhen( myCallBack,
  133.                          myProc,         // UPP (as we're PowerPC friendly)  
  134.                          callBackRefCon, // Passed to the call back 
  135.                          triggerTimeFwd, // or triggerTimeBwd or triggerTimeEither (p2.337)
  136.                          timeToDoIt * moviesTimeScale,   // Time to trigger it
  137.                          moviesTimeScale );
  138.  
  139. We have to store myCallBack and dispose of it using "DisposeCallBack" when we close the window. Information to the call back can be past to it in the callBackRefCon. If we want to store something slightly larger,  we can pass it as handle or pointer in the refCon. This is where  Show Movie comes to the rescue. After NewCallBack,  I create and store the data using "AddNewCallBackInfo" which adds a CallBackInfoHdl into the chain hanging of DocMovieInfoHndl(i.e. the windows refCon) and returns it. When I use CallMeWhen, I pass it as the refCon. So the whole block becomes :
  140.  
  141. QTCallBack      myCallBack;
  142. CallBackInfoHdl hTempCallBackInfo;
  143. SomeDataType    someData;
  144.  
  145. myCallBack = NewCallBack ( moviesTimeBase, callBackAtTime);
  146.  
  147. hTempCallBackInfo = AddNewCallBackInfo( pWindow,
  148.                                                                                                                             myCallBack,
  149.                                                                                                                             (Ptr) &someData, sizeof(SomeDataType),
  150.                                                                                                                             triggerTimeFwd,               // param1
  151.                                                                                                                             timeToDoIt * moviesTimeScale, // param2
  152.                                                                                                                          moviesTimeScale               // param3
  153.                                                                                                                         );
  154.  
  155.     theErr = CallMeWhen ( myCallBack,MyCallBackUpp,
  156.                                                                                         (long)hTempCallBackInfo,
  157.                                                                                         (**hTempCallBackInfo).param1,
  158.                                                                                         (**hTempCallBackInfo).param2,
  159.                                                                                         (**hTempCallBackInfo).param3 );
  160.  
  161. The call back needs to be in the following format:
  162.  
  163. pascal void MyCallBack     (QTCallBack callBack, long refcon);
  164.  
  165. "callBack" refers to the structure created by "NewCallBack". The "refCon" contains the value passed in the 2nd parameter of "CallMeWhen". In the case of "Show Movie" this is a CallBackInfoHdl. At the end of CallBackInfoHdl is a block of data (someData) that we added for this call back to use. Show Movie has a function to extract this called "GetDataFromCBInfo".
  166.  One other thing to bear in mind, is once the call back has been invoked, the Movie Toolbox calls "CancelCallBack", so if you want it to happen again, (say in a loop), we have to reschedule the event using "CallMeWhen" . So our call back now looks like.
  167.  
  168. pascal void MyCallBack     (QTCallBack callBack, long refcon)
  169. {
  170.   SomeDataType someData;
  171.   OSErr        theErr = noErr;
  172.     
  173.   if (GetDataFromCBInfo ( (CallBackInfoHdl)refcon,
  174.                           (Ptr) &someData,
  175.                           sizeof ( SomeDataType ) ))
  176.   {
  177.           // Now do something with it !!!!
  178.   }
  179. // Finally must reschedule the call back so that it can happen again (Optional)    
  180.     theErr = CallMeWhen ( callBack,
  181.                                                     MyCallBackUpp, refcon,
  182.                                                     (**(CallBackInfoHdl)refcon).param1,
  183.                                                     (**(CallBackInfoHdl)refcon).param2,
  184.                                                     (**(CallBackInfoHdl)refcon).param3);
  185. }
  186.  
  187. When the window is closed Show Movie disposes of these structures
  188.  
  189.     RemoveCallBackInfo (  hFirstItem );
  190.  
  191.  
  192.     
  193. Setting call backs based on the length of a movie.
  194.  
  195. Normally, you calculate the position of a call back by multiplying the time in seconds by the movies time scale. To set a call back based on the length of a movie, use GetMovieDuration to return the length of the movie and divide or multiply by the appropriate amount.
  196.  
  197.  E.g. to do something half way through
  198.  
  199.     long      halfWayThrough;
  200.     TimeValue theMovieLen;
  201.  
  202.     theMovieLen  = GetMovieDuration( theMovie );
  203.     halfWayThrough = theMovieLen*0.5;
  204.  
  205. You can use halfWayThrough as the trigger time for the call back
  206.  
  207. Automatically closing a movie once it has finished playing
  208.  
  209. This all done in a routine called "ServiceMovieTasks" in MovieStuff.c. This should be called after WaitNextEvent and it does three things. It scans the window list and checks if the event needs to be passed to a window's movie controller. Next it sees if the window's movie is running using "IsMovieDone". If it has finished and should be closed once finished, it closes the window. Finally, if there is still a movie playing, it calls "MoviesTask" which services all the movies.
  210.  
  211. Boolean ServiceMovieTasks ( WindowPtr pWindow, const EventRecord *theEvent )
  212. {
  213.     Boolean doneProccessing = false;  // goes to true if MCIsPlayerEvent handles event
  214.     DocMovieInfoHndl hDocMovieInfo;   // Current windows movie info
  215.     short   windCount;
  216.     Boolean needMovieTasks = false;   // Goes true if there is a movie running
  217.     Boolean moviePlaying;
  218.     
  219. // Scan window list
  220.     for (windCount = 0;windCount < kMaxWindows; windCount++ )
  221.         if (gTheWinds[windCount])
  222.             {
  223. // Get movie information for this window
  224.                 hDocMovieInfo = (DocMovieInfoHndl)GetWRefCon (gTheWinds[windCount]);
  225.  
  226. // Handle any events to this windows movie controller if it's got one
  227.                 if (!doneProccessing)
  228.                     if ((**hDocMovieInfo).movieControls)
  229.                         if (MCIsPlayerEvent((**hDocMovieInfo).movieControls, theEvent))
  230.                             doneProccessing = true;
  231.     
  232. // Next bit checks to see if a window has finished playing (by using IsMovieDone)
  233. // and closes it if auto close has been set for it
  234.                 moviePlaying = !IsMovieDone((**hDocMovieInfo).actualMovie);
  235.                 if (   (**hDocMovieInfo).movieAutoClose && (!moviePlaying) )
  236.                     CloseOurWindow( windCount );
  237.                 else
  238.                     if (moviePlaying)
  239.                         needMovieTasks = true; // Hey there's a movie running
  240.             }
  241.  
  242. // If we have an active movie, then service all movies
  243.     if (needMovieTasks)
  244.         MoviesTask(nil,0);
  245.  
  246.     return ( doneProccessing );    
  247. }
  248.  
  249. ServiceMovieTasks returns true if it has handled the event, so you're event loop should do no further processing.
  250.  
  251.  
  252. Making a loop within a movie.
  253.  
  254. This is done in "SetupLoop" and the call back "AlterMasterOffset".
  255. The movie is made to loop by changing current time by using "SetTimeBaseValue". To make sure it keeps looping we must reschedule the movie.
  256.  
  257. pascal void AlterMasterOffset(QTCallBack myCallBack,long ref)
  258. {
  259.     DocMovieInfoHndl hDocMovieInfo;
  260.     WindowPtr pWindow;
  261.     TimeValue newTimeValue; // Where we want to move to
  262.     OSErr     theErr;
  263.  
  264. // Extract where we want to move to from (CallBackInfoHdl)ref
  265.     if (GetDataFromCBInfo( ( CallBackInfoHdl)ref,
  266.                                                  (Ptr) &newTimeValue, sizeof ( TimeValue ) ))
  267.     {
  268.         pWindow = (**(CallBackInfoHdl)ref).pParentWindow; // Get the parent window
  269.         hDocMovieInfo = (DocMovieInfoHndl)GetWRefCon ( pWindow ); // Get the movie info
  270.  
  271. // Move our position in the movie
  272.         SetTimeBaseValue ( (**hDocMovieInfo).moviesTimeBase,
  273.                                                                               newTimeValue,
  274.                                                                               (**hDocMovieInfo).moviesTimeScale);
  275.  
  276. // Finally must reschedule the call back so that it happens again
  277.         theErr = CallMeWhen ( myCallBack,
  278.                                                     gAlterMasterOffsetUpp, ref,
  279.                                                     (**(CallBackInfoHdl)ref).param1,
  280.                                                     (**(CallBackInfoHdl)ref).param2,
  281.                                                     (**(CallBackInfoHdl)ref).param3);
  282.     }
  283. }
  284.  
  285. Changing the rate of a movie while it is playing.
  286.  
  287. This is done in "SetupMovieRate" and the call back "AlterRate".
  288. The call back is setup as above, but this time the data is the new rate we want the movie to run at. The call back "AlterRate" extracts the new rate and sets it using "SetMovieRate".
  289.  
  290. pascal void AlterRate( QTCallBack myCallBack,long ref)
  291. {
  292.  
  293.     DocMovieInfoHndl hDocMovieInfo;
  294.     WindowPtr pWindow;
  295.     long      newMovieRate;
  296.  
  297. // Extract the new speed we want the movie to run at
  298.     if (GetDataFromCBInfo ( ( CallBackInfoHdl)ref,
  299.                          (Ptr) &newMovieRate,
  300.                          sizeof ( long ) ))
  301.     {
  302.         pWindow = (**(CallBackInfoHdl)ref).pParentWindow;  // Get the parent window
  303.         hDocMovieInfo = (DocMovieInfoHndl)GetWRefCon ( pWindow ); // Get the movie info
  304.  
  305.  
  306. // Change the movies rate
  307.         SetMovieRate((**hDocMovieInfo).actualMovie, newMovieRate);
  308.     }
  309.  
  310.     return;
  311. }
  312.  
  313. Slaving a movie to another.
  314. Offsetting the start frame of a slave movie.
  315. Delaying the start of a slave movie playing
  316.  
  317. This is done in "SetupSlaveMovie".
  318. First set up the master time base value to 0 ( the start ) : 
  319.  
  320.     SetTimeBaseValue( (**hDocMasterInfo).moviesTimeBase,
  321.                       0,
  322.                       (**hDocMasterInfo).moviesTimeScale);
  323.  
  324. Now slave second movie to first. 
  325.     
  326.     SetMovieMasterTimeBase( (**hDocSlaveInfo).actualMovie,
  327.                             (**hDocMasterInfo).moviesTimeBase,
  328.                             nil);
  329.  
  330. The next bit calculates the offset between the two movies. We use SetTimeBaseValue to do this and as the slave uses the master's time base, what we do is set up an offset between the two movies.
  331.  
  332.     SetTimeBaseValue( (**hDocSlaveInfo).moviesTimeBase,
  333.                       slaveTimeOffset,
  334.                       (**hDocSlaveInfo).moviesTimeScale);
  335.  
  336. Finally we need to put a call back into the master movie to make sure the slave starts when the master starts. We can also use this to add a delay to the start of the slave movie.
  337. We calculate when we want to start the movie, then add a call back that starts the movie at the appropriate time. We put this call back into the masters time base.
  338.  
  339. Default Buttons in dialogs
  340.  
  341. Most applications put a default box round the OK button by creating a user item and drawing a round rect. System 7 included some new calls to the dialog manager that were not discussed in Inside Mac but are buried in a Technote ( Toolbox - TB 37 - Pending Update Perils). "SetDialogDefaultItem" sets the Ok button,  "SetDialogCancelItem" sets the cancel item, "SetDialogTracksCursor" converts the cursor to an I Beam when you mouse over a text edit field. The gotcha is that you must call the StdFilterProc. You can get this with "GetStdFilterProc". If you have a custom filter proc, it must call this as well. I strongly suggest looking at Technote TB 37 for a fuller explanation.
  342. Anyway armed with this knowledge, my about box looks like:
  343.  
  344. void AboutBox(void)
  345. {
  346.  GrafPtr   savePort = nil;
  347.  DialogPtr aboutDialog;
  348.  ModalFilterUPP theFilter = nil;
  349.  short     itemHit = 0;   // dialog item we've clicked on
  350.     
  351. // Get the dialog box resource
  352.     aboutDialog = GetNewDialog(9041, nil, (WindowPtr) -1 );
  353.  
  354.   GetPort(&savePort);
  355.   SetPort( aboutDialog );
  356.  
  357.     ShowWindow( aboutDialog );
  358.         
  359. // Get the standard filter proc
  360.   if (GetStdFilterProc(&theFilter) != noErr)
  361.       DebugStr("\pFailed to get standard dialog filter.");
  362.   
  363. // Set item 1 - <OK> to have a default box around it
  364.     SetDialogDefaultItem(aboutDialog,1);
  365.   
  366. // Modal dialog loop    
  367. do     {
  368. // Use "theFilter" in ModalDialog call
  369.        ModalDialog(theFilter,&itemHit);
  370.     } while (itemHit != 1);
  371.  
  372.   DisposeDialog(aboutDialog);
  373.   SetPort(savePort);
  374. }
  375.  
  376. Because I'm using the standard filter, I don't have to worry about checking for the a [Return] key press or drawing a box around the <OK> button.
  377.  
  378. Further things YOU could do
  379.  
  380. If you feel like playing with this app, there's a few ways you could improve it.
  381.  
  382. - Make the movie loop from the end back to the start.
  383. Use "callBackAtExtremes" in NewCallBack and "triggerAtStop" in CallMeWhen
  384.  
  385. - Allow the user to enter his own values for when it loops. 
  386. - More flexibility about when the rate changes and by how much.
  387. - Allow user to enter the offset and delay values for slave.
  388. All these needs are a nice user interface. You could also change them so they handle fractions of a second.
  389.  
  390. - Dynamically change options
  391. You could do this by using "NewCallBack" to create  all the call backs you might need but only insert the ones that you initially need (using "CallMeWhen"). "CancelCallBack" and "CallMeWhen" could be used to extract and insert call backs when you wanted them, though you'll need some modifications to the data structure to identify which call back does what.
  392.  
  393. Don Swatman - Sept. 95
  394. © 1995 by Apple Computer, Inc. All rights reserved.
  395.  
  396.